package org.fnzn.util.xml;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.json.*;
import org.apache.commons.io.IOUtils;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;


import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Iterator;

/**
 * @author lengleng
 * @date 2018/05/17
 */
public class XmlUtils {
    private static <T> T XML2Object(Class<T> clazz, String xml) {
        try {
            if (xml == null) {
                return null;
            }
            JAXBContext ctx = JAXBContext.newInstance(clazz);
            Unmarshaller unmarshaller = ctx.createUnmarshaller();

            StringReader reader = new StringReader(xml);
            SAXParserFactory sax = SAXParserFactory.newInstance();
            sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            sax.setNamespaceAware(false);//忽略命名空间
            XMLReader xmlReader = sax.newSAXParser().getXMLReader();
            Source source = new SAXSource(xmlReader, new InputSource(reader));
            Object result = unmarshaller.unmarshal(source);
            return (T) result;
        } catch (Exception e) {
        }
        return null;
    }

    private static <T> String object2XML(T t, String charset) {
        String result = null;
        StringWriter writer = new StringWriter();
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(t.getClass());
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
            marshaller.setProperty(Marshaller.JAXB_ENCODING, charset);
            OutputFormat format = new OutputFormat();
            format.setEncoding(charset);
            format.setIndent(false);
            format.setNewlines(false);
            format.setNewLineAfterDeclaration(false);
            XMLWriter xmlWriter = new XMLWriter(writer, format);
            xmlWriter.setEscapeText(false);
            XMLFilterImpl nsFilter = new XMLFilterImpl() {
                private boolean ignoreNamespace = false;
                //				private String rootNamespace = null;
                private boolean isRootElement = true;

                @Override
                public void startPrefixMapping(String prefix, String url) throws SAXException {
                    if (!this.ignoreNamespace) {
                        super.startPrefixMapping("", url);
                    }
                }

                @Override
                public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                    if (this.ignoreNamespace) {
                        uri = "";
                    }
                    if (this.isRootElement) {
                        this.isRootElement = false;
                    } else if (!"".equals(uri) && !"xmlns".contains(localName)) {
                        localName = localName + " xmlns=\"" + uri + "\"";
                    }
                    super.startElement(uri, localName, localName, attributes);
                }

                @Override
                public void endElement(String uri, String localName, String qName) throws SAXException {
                    if (this.ignoreNamespace) {
                        uri = "";
                    }
                    super.endElement(uri, localName, localName);
                }

            };

            nsFilter.setContentHandler(xmlWriter);
            marshaller.marshal(t, nsFilter);
            result = writer.toString();
        } catch (Exception ex) {
        } finally {
            IOUtils.closeQuietly(writer);
        }
        return result;
    }

    /**
     * 对象转换成XML报文
     * 针对兴业信贷核心
     *
     * @param head 请求报文头
     * @param t    业务对象
     * @param <T>  泛型
     * @return XML
     */
    public static <T> String req2XML(Head head, T t) {
        JSONObject jsonObject = new JSONObject(t,false);
        Request request = new Request();
        request.setHead(head);
        request.setBody(toXml(jsonObject,null));
        return object2XML(request, "utf-8");
    }

    /**
     * 返回兴业的保温对象
     * @param respXml 响应报文
     * @param clazz 业务对象类型
     * @param <T> 业务对象
     * @return
     */
    public static <T> Response<T> xml2Resp(String respXml,Class<T> clazz) {
        Response respone = XML2Object(Response.class,respXml);
        if(clazz!=null){
	        Node body = XmlUtil.readXML(respXml).getElementsByTagName("body").item(0);
	        T t = BeanUtil.mapToBean(XmlUtil.xmlToMap(body),clazz,true);
	        assert respone != null;
	        respone.setBody(t);
        }
        return respone;
    }

    public static String toXml(Object object, String tagName) throws JSONException {
        StringBuilder sb = new StringBuilder();
        JSONArray ja;
        JSONObject jo;
        String key;
        Iterator<String> keys;
        String string;
        Object value;

        if (object instanceof JSONObject) {

            // Emit <tagName>
            if (tagName != null) {
                sb.append('<');
                sb.append(tagName);
                sb.append('>');
            }

            // Loop thru the keys.
            jo = (JSONObject) object;
            keys = jo.keySet().iterator();
            while (keys.hasNext()) {
                key = keys.next();
                value = jo.get(key);
                if (value == null) {
                    value = "";
                } else if (value.getClass().isArray()) {
                    value = new JSONArray(value);
                }
                string = value instanceof String ? (String) value : null;

                // Emit content in body
                if ("content".equals(key)) {
                    if (value instanceof JSONArray) {
                        ja = (JSONArray) value;
                        int i = 0;
                        for (Object val : ja) {
                            if (i > 0) {
                                sb.append('\n');
                            }
                            sb.append(XmlUtil.escape(val.toString()));
                            i++;
                        }
                    } else {
                        sb.append(XmlUtil.escape(value.toString()));
                    }

                    // Emit an array of similar keys

                } else if (value instanceof JSONArray) {
                    ja = (JSONArray) value;
                    for (Object val : ja) {
                        if (val instanceof JSONArray) {
                            sb.append('<');
                            sb.append(key);
                            sb.append('>');
                            sb.append(toXml(val,null));
                            sb.append("</");
                            sb.append(key);
                            sb.append('>');
                        } else {
                            sb.append(toXml(val, key));
                        }
                    }
                } else if ("".equals(value) || value instanceof JSONNull) {
                    sb.append('<');
                    sb.append(key);
                    sb.append("/>");


                    // Emit a new tag <k>

                } else {
                    sb.append(toXml(value, key));
                }
            }
            if (tagName != null) {

                // Emit the </tagname> close tag
                sb.append("<");
                sb.append(tagName);
                sb.append('>');
            }
            return sb.toString();

        }

        if (object != null) {
            if (object.getClass().isArray()) {
                object = new JSONArray(object);
            }

            if (object instanceof JSONArray) {
                ja = (JSONArray) object;
                for (Object val : ja) {
                    // XML does not have good support for arrays. If an array
                    // appears in a place where XML is lacking, synthesize an
                    // <array> element.
                    sb.append(toXml(val, tagName == null ? "array" : tagName));
                }
                return sb.toString();
            }
        }

        string = (object == null) ? "null" : XmlUtil.escape(object.toString());
        return (tagName == null) ? "\"" + string + "\"" : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName + ">" + string + "</" + tagName + ">";

    }
}
